namespace RBAC.Domain.HXJX
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// 生产工单-工序明细 领域行为（partial）。
    /// 说明：为不影响实体的数据库映射，行为方法与规则以部分类形式提供。
    /// 覆盖内容：报工、转序、在制计算、计划时间设置、行级校验、序号唯一性与自动重排、审批前置判断等。
    /// </summary>
    public partial class ProductionWorkOrderProcess
    {
        /// <summary>
        /// 设置计划开始/结束时间并进行区间合法性校验。
        /// </summary>
        /// <param name="start">计划开始时间。</param>
        /// <param name="end">计划结束时间。</param>
        /// <exception cref="ArgumentException">当开始时间晚于结束时间时抛出。</exception>
        public void SetPlan(DateTime start, DateTime end)
        {
            if (start > end)
                throw new ArgumentException("Plan start time cannot be after end time.");

            PlanStartTime = start;
            PlanEndTime = end;
        }

        /// <summary>
        /// 报工数量基础校验：数量需大于 0。
        /// </summary>
        /// <param name="qty">报工数量。</param>
        /// <exception cref="ArgumentException">当数量小于等于 0 时抛出。</exception>
        public void ValidateReportQty(decimal qty)
        {
            if (qty <= 0)
                throw new ArgumentException("Report quantity must be greater than 0.");
        }

        /// <summary>
        /// 转序数量校验：数量需大于 0 且不得超过当前在制数量（WIP）。
        /// </summary>
        /// <param name="qty">拟转序数量。</param>
        /// <exception cref="ArgumentException">当数量非法或超过在制时抛出。</exception>
        public void ValidateTransferQty(decimal qty)
        {
            if (qty <= 0)
                throw new ArgumentException("Transfer quantity must be greater than 0.");

            var wip = GetWipCalculated();
            if (qty > wip)
                throw new ArgumentException($"Transfer quantity {qty} cannot exceed current WIP {wip}.");
        }

        /// <summary>
        /// 良品报工：累加 <see cref="GoodQty"/> 并重算在制 <see cref="WipQty"/>。
        /// 可携带业务时间与备注信息用于审计或日志。
        /// </summary>
        /// <param name="qty">良品数量（必大于 0）。</param>
        /// <param name="time">业务发生时间（不在本实体持久化）。</param>
        /// <param name="remark">备注，可为空。</param>
        public void ReportGood(decimal qty, DateTime time, string? remark = null)
        {
            ValidateReportQty(qty);

            GoodQty = (GoodQty ?? 0m) + qty;
            // Recalculate WIP based on updated totals
            RecalculateWip();

            // Optionally append remark
            AppendRemarkPrefix($"[GOOD {qty} @ {time:yyyy-MM-dd HH:mm}] ", remark);
        }

        /// <summary>
        /// 不良报工：累加 <see cref="ScrapQty"/> 并重算在制 <see cref="WipQty"/>。
        /// 需要提供不良原因，便于后续质量统计与分析。
        /// </summary>
        /// <param name="qty">不良数量（必大于 0）。</param>
        /// <param name="reason">不良原因（必填）。</param>
        /// <param name="time">业务发生时间（不在本实体持久化）。</param>
        /// <param name="remark">备注，可为空。</param>
        public void ReportScrap(decimal qty, string reason, DateTime time, string? remark = null)
        {
            if (string.IsNullOrWhiteSpace(reason))
                throw new ArgumentException("Scrap reason is required.");

            ValidateReportQty(qty);

            ScrapQty = (ScrapQty ?? 0m) + qty;
            RecalculateWip();

            AppendRemarkPrefix($"[SCRAP {qty}, REASON={reason} @ {time:yyyy-MM-dd HH:mm}] ", remark);
        }

        /// <summary>
        /// 转序：将数量计入 <see cref="TransferQty"/> 并重算在制 <see cref="WipQty"/>。
        /// 默认口径：在制 = 转序 - 良品 - 不良；超出在制将被拒绝。
        /// </summary>
        /// <param name="qty">转序数量（必大于 0 且不超过当前在制）。</param>
        /// <param name="time">业务发生时间（不在本实体持久化）。</param>
        /// <param name="remark">备注，可为空。</param>
        public void TransferToNext(decimal qty, DateTime time, string? remark = null)
        {
            ValidateTransferQty(qty);

            TransferQty = (TransferQty ?? 0m) + qty;
            RecalculateWip();

            AppendRemarkPrefix($"[TRANSFER {qty} @ {time:yyyy-MM-dd HH:mm}] ", remark);
        }

        /// <summary>
        /// 重算在制数量（WIP）。
        /// 计算口径：WIP = max(0, TransferQty - GoodQty - ScrapQty)。
        /// </summary>
        public void RecalculateWip()
        {
            var transfer = TransferQty ?? 0m;
            var good = GoodQty ?? 0m;
            var scrap = ScrapQty ?? 0m;
            var wip = transfer - good - scrap;
            WipQty = wip < 0m ? 0m : wip;
        }

        /// <summary>
        /// 仅返回计算得到的在制数量，不修改当前实体状态。
        /// </summary>
        public decimal GetWipCalculated()
        {
            var transfer = TransferQty ?? 0m;
            var good = GoodQty ?? 0m;
            var scrap = ScrapQty ?? 0m;
            var wip = transfer - good - scrap;
            return wip < 0m ? 0m : wip;
        }

        /// <summary>
        /// 获取完成率。
        /// 当 <see cref="TransferQty"/> 小于等于 0 时返回 0；否则返回 GoodQty/TransferQty，并限制在 [0,1]。
        /// </summary>
        public decimal GetProgress()
        {
            var transfer = TransferQty ?? 0m;
            if (transfer <= 0m) return 0m;
            var good = GoodQty ?? 0m;
            var rate = good / transfer;
            if (rate < 0m) return 0m;
            if (rate > 1m) return 1m;
            return rate;
        }

        /// <summary>
        /// 计算与计划数量的差异：planQty - (GoodQty + ScrapQty)。
        /// 返回值 &gt; 0 表示尚未完成；&lt; 0 表示已超出计划。
        /// </summary>
        /// <param name="planQty">当前工序的计划报工数量。</param>
        public decimal GetBalanceToPlan(decimal planQty)
        {
            var actual = (GoodQty ?? 0m) + (ScrapQty ?? 0m);
            return planQty - actual;
        }

        /// <summary>
        /// 行级校验（保存前）：
        /// - 序号 SeqNo &gt; 0，工序编码/名称必填；
        /// - 标准工时 StdHours ≥ 0；
        /// - 若填写了计划时间则需满足开始 ≤ 结束；
        /// - 各数量字段（转序/良品/不良/在制）不得为负数。
        /// </summary>
        public void ValidateForSave()
        {
            if (SeqNo <= 0)
                throw new ArgumentException("SeqNo must be a positive integer.");
            if (string.IsNullOrWhiteSpace(ProcessCode))
                throw new ArgumentException("ProcessCode is required.");
            if (string.IsNullOrWhiteSpace(ProcessName))
                throw new ArgumentException("ProcessName is required.");
            if (StdHours.HasValue && StdHours.Value < 0m)
                throw new ArgumentException("StdHours cannot be negative.");
            if (PlanStartTime.HasValue && PlanEndTime.HasValue && PlanStartTime > PlanEndTime)
                throw new ArgumentException("PlanStartTime cannot be after PlanEndTime.");

            if ((TransferQty ?? 0m) < 0m) throw new ArgumentException("TransferQty cannot be negative.");
            if ((GoodQty ?? 0m) < 0m) throw new ArgumentException("GoodQty cannot be negative.");
            if ((ScrapQty ?? 0m) < 0m) throw new ArgumentException("ScrapQty cannot be negative.");
            if ((WipQty ?? 0m) < 0m) throw new ArgumentException("WipQty cannot be negative.");
        }

        /// <summary>
        /// 校验集合内工序序号唯一性（同一工单内不允许重复）。
        /// 若存在重复序号则抛出异常。
        /// </summary>
        public static void EnsureSeqNoUnique(IEnumerable<ProductionWorkOrderProcess> items)
        {
            if (items == null) throw new ArgumentNullException(nameof(items));
            var dup = items.GroupBy(x => x.SeqNo).FirstOrDefault(g => g.Key > 0 && g.Count() > 1);
            if (dup != null)
                throw new ArgumentException($"Duplicate SeqNo detected: {dup.Key}.");
        }

        /// <summary>
        /// 自动重排序号：按当前 SeqNo 升序（同序则按 Id）重排，并以等差方式重新编号。
        /// 默认从 10 开始，每步 10（便于增删后插入）。
        /// </summary>
        public static void AutoReorderSeqNo(IList<ProductionWorkOrderProcess> items, int start = 10, int step = 10)
        {
            if (items == null) throw new ArgumentNullException(nameof(items));
            if (step <= 0) throw new ArgumentException("step must be > 0");

            var sorted = items.OrderBy(i => i.SeqNo).ThenBy(i => i.Id).ToList();
            var val = start;
            foreach (var it in sorted)
            {
                it.SeqNo = val;
                val += step;
            }
        }

        /// <summary>
        /// 是否满足“可提交审批”的最低要求：BOM 或 工序至少存在其一。
        /// 通过计数入参避免跨实体强耦合。
        /// </summary>
        public static bool CanSubmitForApproval(int bomCount, int processCount)
        {
            return bomCount > 0 || processCount > 0;
        }

        private void AppendRemarkPrefix(string prefix, string? remark)
        {
            var note = string.IsNullOrWhiteSpace(remark) ? string.Empty : remark!.Trim();
            if (string.IsNullOrEmpty(Remark))
                Remark = prefix + note;
            else
                Remark = Remark + " | " + prefix + note;
        }
    }
}
